//
// Manta - Structural Variant and Indel Caller
// Copyright (c) 2013-2019 Illumina, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//

/// \file
/// \author Chris Saunders
///

#pragma once

#include "blt_util/pos_processor_base.hpp"

#include "blt_util/pos_range.hpp"

#include <cassert>

#include <iosfwd>
#include <map>
#include <vector>

/// \brief describes stages to the stage_manager
///
/// Each stage has an integer id.
///
/// All stages are related to each other by a tree. Edge distance on
/// this tree represent bases of the reference. A root stage must be
/// defined first, all following stages must have a parent stage and
/// a distance to the parent.
///
/// \example As used by the stage_manager, a simple two-stage tree
/// where the stages are separated by 100 cycles would mean that the
/// root stage is executed at the reference position pointer (as
/// always), but the second stage is executed at positions 100 bases
/// behind the reference position pointer.
///
struct stage_data {
  // position,stage_id pair, where position is total distance of this stage from the root stage:
  typedef std::pair<unsigned, int> pos_stage_id;
  // pos_stage_ids, sorted by increasing position and stage id:
  typedef std::vector<pos_stage_id> stage_pos_t;

  /// \brief Add a "root" stage:
  void add_stage(const int id) { return add_stage(id, 0, 0, false); }

  /// \brief Add a child stage which follows at a certain distance from its
  /// parent
  ///
  /// parent id must already have been entered
  ///
  void add_stage(
      const int id, const int parent_id, const unsigned parent_distance, const bool is_parent = true);

  /// The stages are summarized to the stage manager through the
  /// following interface:
  ///
  const stage_pos_t& stage_pos() const { return _stage_pos; }

  /// lookup total distance from root stage for any stage id:
  ///
  unsigned get_stage_id_shift(const int id) const
  {
    idmap_t::const_iterator i(_ids.find(id));
    if (i == _ids.end()) unknown_id_error(id);
    return i->second;
  }

  /// debug output:
  void dump(std::ostream& os) const;

private:
  void unknown_id_error(const int id) const;

  // map of stage-id -> total distance from the root stage
  typedef std::map<int, unsigned> idmap_t;

  idmap_t     _ids;
  stage_pos_t _stage_pos;
};

/// \brief help to manage information which is being gathered in an
/// approximately sequential fashion and processed in sequence in
/// multiple stages.
///
/// assumes that information related to each position will be
/// available in an approximately sequential fashion, where all
/// position values submitted after position X will be greater than
/// (X - first_stage_buffer_size + 1). A violation of this assumption will
/// trigger a runtime error.
///
/// range policy:
///
/// if begin_pos is not specified, then event processing and
/// reporting start at the first pos >= 0 with position information
/// submitted, else at begin_pos
///
/// if end_pos is not specified, then processing ends after last_pos
/// with information submitted, else at end_pos.
///
struct stage_manager {
  // stage_data structure is described above
  //
  // report_range is what is sounds like
  //
  // pos_processor_base is where stage manager signals stage
  // execution to
  //
  stage_manager(const stage_data& sdata, const pos_range& report_range, pos_processor_base& ppb);

  // Process any remaining positions
  void reset();

  // Handle new pos value is used to indicate a possible advance of
  // the head position -- this represents the position of the input
  // information. Note that you must explicitly check whether pos is
  // too low for any particular stage using the methods further
  // below, handle_new_pos_value does not do this for you.
  //
  // When the head position is advanced, it triggers a series of
  // stage processing steps along the way. The head position is the
  // position from which the distances used in stage_data are
  // measured.
  //
  // Example: extending the stage_data example above -- if
  // stage_data has a root_stage (distance 0) and a child_stage
  // (distance 100 from root), and the current head_position is
  // 1000, then setting handle_new_pos_value(1002) would cause the
  // following sequence of calls to pos_process_base:
  //
  // process_pos(1001,root_stage_id);
  // process_pos(901,child_stage_id);
  // process_pos(1002,root_stage_id);
  // process_pos(902,child_stage_id);
  //
  void handle_new_pos_value(const pos_t pos);

  // Return true if stage 'stage_id' has not been run on position
  // yet (ie. pos value is not too low).
  //
  bool is_new_pos_value_valid(const pos_t pos, const int stage_id);

  // Test as above, except that an exception is thrown if the
  // position is too low.
  //
  void validate_new_pos_value(const pos_t pos, const int stage_id);

  pos_t max_pos() const { return _max_pos; }

  pos_t min_pos() const { return _min_pos; }

  bool is_first_pos_set() const { return _is_first_pos_set; }

  // Revising stage data is very restricted -- the new sdata
  // must have the the same number of stages, with the same set
  // of ids. Stages may only increase in length relative to their
  // prior values.
  //
  void revise_stage_data(const stage_data& sdata);

  const stage_data& get_stage_data() const { return _sdata; }

private:
  // advances head position from its current value to pos,
  // signaling all stage processing steps to pos_process_base along
  // the way:
  //
  void process_pos(const pos_t pos);

  // advances head position until all remaining stage processing is
  // complete based on the current head position value.
  //
  void finish_process_pos();

  stage_data _sdata;

  bool  _is_head_pos;
  pos_t _head_pos;
  pos_t _max_pos;
  pos_t _min_pos;
  bool  _is_first_pos_set;

  const pos_range     _report_range;
  pos_processor_base& _ppb;

  const stage_data::stage_pos_t* _stage_pos_ptr;
  unsigned                       _stage_size;
  std::vector<uint8_t>           _is_minpos;  //faster lu than vector<bool>
  std::vector<pos_t>             _minpos;
  bool                           _is_any_minpos;
};
